package nyist.media.utils;

import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.Assert;
import org.springframework.util.SerializationUtils;

import javax.crypto.Cipher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Base64;
import java.util.concurrent.TimeUnit;

/**
 * Utils - RSA加密解密
 */
public final class RSAUtils {

    /**
     * 安全服务提供者
     */
    private static final Provider PROVIDER = new BouncyCastleProvider();

    /**
     * "私钥"参数名称
     */
    private static final String PRIVATE_KEY_REDIS_KEY_PREFIX = "rsa:private:key:";

    /**
     * "私钥"参数名称
     */
    private static final String PRIVATE_KEY_ATTRIBUTE_NAME = RSAUtils.class.getName() + ".PRIVATE_KEY";

    /**
     * 密钥大小
     */
    private static final int KEY_SIZE = 1024;

    /**
     * 不可实例化
     */
    private RSAUtils() {
    }

    /**
     * 生成密钥对
     *
     * @return 密钥对
     */
    public static KeyPair generateKeyPair() {
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", PROVIDER);
            keyPairGenerator.initialize(KEY_SIZE, new SecureRandom());
            return keyPairGenerator.generateKeyPair();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 加密
     *
     * @param publicKey 公钥
     * @param data      数据
     * @return 加密后的数据
     */
    public static byte[] encrypt(PublicKey publicKey, byte[] data) {
        Assert.notNull(publicKey, "参数错误");
        Assert.notNull(data, "参数错误");
        try {
            Cipher cipher = Cipher.getInstance("RSA", PROVIDER);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return cipher.doFinal(data);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 加密
     *
     * @param publicKey 公钥
     * @param text      字符串
     * @return Base64编码字符串
     */
    public static String encrypt(PublicKey publicKey, String text) {
        Assert.notNull(publicKey, "参数错误");
        Assert.notNull(text, "参数错误");
        byte[] data = encrypt(publicKey, text.getBytes());
        return data != null ? Base64.getEncoder().encodeToString(data) : null;
    }

    /**
     * 解密
     *
     * @param privateKey 私钥
     * @param data       数据
     * @return 解密后的数据
     */
    public static byte[] decrypt(PrivateKey privateKey, byte[] data) {
        Assert.notNull(privateKey, "参数错误");
        Assert.notNull(data, "参数错误");
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", PROVIDER);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return cipher.doFinal(data);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 解密
     *
     * @param privateKey 私钥
     * @param text       Base64编码字符串
     * @return 解密后的数据
     */
    public static String decrypt(PrivateKey privateKey, String text) {
        Assert.notNull(privateKey, "参数错误");
        Assert.notNull(text, "参数错误");
        byte[] data = decrypt(privateKey, Base64.getDecoder().decode(text));
        return data != null ? new String(data) : null;
    }

    /**
     * 生成密钥(添加私钥至Session并返回公钥)
     *
     * @param template redis客户端
     * @param uuid     唯一标识
     * @return 公钥
     */
    public static RSAPublicKey generateKey(StringRedisTemplate template, String uuid) {
        KeyPair keyPair = RSAUtils.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        template.opsForValue().set(PRIVATE_KEY_REDIS_KEY_PREFIX + uuid, Base64.getEncoder().encodeToString(SerializationUtils.serialize(privateKey)), 10, TimeUnit.MINUTES);
        return publicKey;
    }

    /**
     * 生成密钥(添加私钥至Session并返回公钥)
     *
     * @param request httpServletRequest
     * @return 公钥
     */
    public static RSAPublicKey generateKey(HttpServletRequest request) {
        KeyPair keyPair = RSAUtils.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        HttpSession session = request.getSession();
        session.setAttribute(PRIVATE_KEY_ATTRIBUTE_NAME, privateKey);
        return publicKey;
    }

    /**
     * 移除私钥
     *
     * @param request httpServletRequest
     */
    public static void removePrivateKey(HttpServletRequest request) {
        HttpSession session = request.getSession();
        session.removeAttribute(PRIVATE_KEY_ATTRIBUTE_NAME);
    }

    /**
     * 移除私钥
     *
     * @param template redis客户端
     * @param uuid     唯一标识
     */
    public static void removePrivateKey(StringRedisTemplate template, String uuid) {
        template.delete(PRIVATE_KEY_REDIS_KEY_PREFIX + uuid);
    }

    /**
     * 解密参数
     *
     * @param template  redis客户端
     * @param uuid      唯一标识
     * @param parameter 参数值，例如加密后的密码
     * @return 解密内容
     */
    public static String decryptParameter(StringRedisTemplate template, String uuid, String parameter) {
        if (parameter != null) {
            RSAPrivateKey privateKey = (RSAPrivateKey)SerializationUtils.deserialize(Base64.getDecoder().decode(template.opsForValue().get(PRIVATE_KEY_REDIS_KEY_PREFIX + uuid)));
            if (privateKey != null) {
                return RSAUtils.decrypt(privateKey, parameter);
            }
        }
        return null;
    }

    /**
     * 解密参数
     *
     * @param name    参数名称
     * @param request httpServletRequest
     * @return 解密内容
     */
    public static String decryptParameter(String name, HttpServletRequest request){
        if (name != null) {
            HttpSession session = request.getSession();
            RSAPrivateKey privateKey = (RSAPrivateKey) session.getAttribute(PRIVATE_KEY_ATTRIBUTE_NAME);
            String parameter = request.getParameter(name);
            if (privateKey != null && StringUtils.isNotEmpty(parameter)) {
                return RSAUtils.decrypt(privateKey, parameter);
            }
        }
        return null;
    }

    public static void main(String[] args) {
        KeyPair keyPair = generateKeyPair();
        System.out.println(keyPair.getPublic());
    }
}